package com.yhx.security.gfoBank.api.com.gmcrypto.mf.gm;

import java.security.SecureRandom;

import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.params.KeyParameter;

/**
 * sm4�ӽ��ܹ�����
 * <p>��Ϊ���ݼӽ��ܶ��Ƕ��ֽ����ݼӽ��ܣ������Ҫע�����ǰ�ͽ��ܺ�ʹ�õ��ַ�������һ��
 * <p>��������˵�����ӿڽ��յĶ���ԭʼ�Ķ��������ݣ���hex����base64��������ݣ���ؽ���֮���ٴ�����
 * @author liangruxing-yfzx
 *
 */
public class SM4Util {

	
    private static final int SM4_ENCRYPT = 1;
	private static final int SM4_DECRYPT = 0;
	public static final int SM4_PKCS8PADDING = 1;
	public static final int SM4_NOPADDING = 0;
    public static final int SM4_KEY_128 = 128;
    

    /**
     * ����sm4��Կ������ʹ��
     * @param keySize ��Կλ����ͨ��SM4Util�ĳ�����ȡ����ֵ��
     * @return sm4��Կ
     */
    public static byte[] generateKey(int keySize){
    	byte[] key = new byte[keySize/8];
    	SecureRandom sr = new SecureRandom();
    	sr.nextBytes(key);
    		
        return key;
    }

    /**
     * sm4 ecbģʽ�������ݣ����ݳ��ȷ�16��������ʹ��Ĭ��PKCS8PADDING��ʽ���
     * @param data �����ܵ�����
     * @param key sm4��Կ
     * @return ��������
     */
    public static byte[] encryptECB(byte[] data,byte[] key) {
    	return encryptECB(data,key,SM4_PKCS8PADDING);
    }
	
    /**
     * sm4 ecbģʽ�������ݣ�ʹ��Ĭ��PKCS8PADDING��ʽȥ�����
     * @param cipher ��������
     * @param key sm4��Կ
     * @return �����ֽ�����
     */
    public static byte[] decryptECB(byte[] cipher,byte[] key) {
    	return decryptECB(cipher,key,SM4_PKCS8PADDING);
    }
    
    /**
     * sm4 CBCģʽ�������ݣ����ݳ��ȷ�16��������ʹ��Ĭ��PKCS8PADDING��ʽ���
     * @param data ����������
     * @param key sm4��Կ
     * @param iv ����
     * @return ��������
     */
    public static byte[] encryptCBC(byte[] data,byte[] key,byte[] iv) {
    	return encryptCBC(data, key, iv, SM4_PKCS8PADDING);
    }
    
    /**
     * sm4 cbcģʽ�������ݣ�ʹ��Ĭ��PKCS8PADDING��ʽȥ�����
     * @param cipher sm4��������
     * @param key sm4��Կ
     * @param iv ����
     * @return �����ֽ�����
     */
    public static byte[] decryptCBC(byte[] cipher,byte[] key,byte[] iv) {
    	return decryptCBC(cipher, key, iv, SM4_PKCS8PADDING);
    }
    
    /**
     * sm4 ecbģʽ��������
     * @param data ����������
     * @param key sm4��Կ
     * @param paddingMode ���ģʽ������֧���뿴��ĳ����ֶ�,��ʹ�ò�֧�ֵ�ģʽ���Ĭ�������
     * @return ������������
     */
	public static byte[] encryptECB(byte[] data,byte[] key,int paddingMode)
	{
		BlockCipher engine = new SM4Engine();
		engine.init(true, new KeyParameter(key));
		if(paddingMode == SM4_PKCS8PADDING) {
			data = padding(data, SM4_ENCRYPT);
		}else {
			data = data.clone();
		}
		int length = data.length;
		for(int i = 0; length > 0; length -= 16,i += 16)
		{
			engine.processBlock(data, i, data, i);
		}
		return data;
	}

	/**
	 * sm4 ecbģʽ��������
	 * @param cipher ��������
	 * @param key sm4��Կ
	 * @param paddingMode ���ģʽ������֧���뿴��ĳ����ֶ�,��ʹ�ò�֧�ֵ�ģʽ���Ĭ�������
	 * @return ���������ֽ�����
	 */
	public static byte[] decryptECB(byte[] cipher,byte[] key,int paddingMode)
	{
		BlockCipher engine = new SM4Engine();
		engine.init(false, new KeyParameter(key));
		int length = cipher.length;
		byte[] tmp = new byte[cipher.length];
		for(int i = 0; length > 0; length -= 16,i += 16)
		{
			engine.processBlock(cipher, i, tmp, i);
		}
		byte[] plain = null;
		if(paddingMode == SM4_PKCS8PADDING) {
			plain = padding(tmp, SM4_DECRYPT);
		}else {
			plain = tmp;
		}
		return plain;
	}

	/**
	 * CBCģʽ��������
	 * @param data ����������
	 * @param key ��Կ
	 * @param iv ����
	 * @param paddingMode ���ģʽ������֧���뿴��ĳ����ֶ�,��ʹ�ò�֧�ֵ�ģʽ���Ĭ�������
	 * @return ��������ֵ
	 */
	public static byte[] encryptCBC(byte[] data,byte[] key,byte[] iv,int paddingMode)
	{
		BlockCipher engine = new SM4Engine();
		engine.init(true, new KeyParameter(key));
		if(paddingMode == SM4_PKCS8PADDING) {
			data = padding(data, SM4_ENCRYPT);
		}else {
			data = data.clone();
		}
		int length = data.length;
		iv = iv.clone();
		for(int i = 0; length > 0; length -= 16,i += 16)
		{

			for (int j = 0; j < 16; j++)
			{
				data[i+j] = ((byte) (data[i+j] ^ iv[j]));
			}
			engine.processBlock(data, i, data, i);
			System.arraycopy(data, i, iv, 0, 16);
		}
		return data;
	}

	/**
	 * CBCģʽ��������
	 * @param cipher ��������
	 * @param key ��Կ
	 * @param iv ����
	 * @param isPadding ���ģʽ������֧���뿴��ĳ����ֶ�,��ʹ�ò�֧�ֵ�ģʽ���Ĭ�������
	 * @return ���������ֽ�����
	 */
	public static byte[] decryptCBC(byte[] cipher,byte[] key,byte[] iv,int paddingMode)
	{
		BlockCipher engine = new SM4Engine();
		engine.init(false, new KeyParameter(key));
		int length = cipher.length;
		byte[] plain = new byte[cipher.length];
		iv = iv.clone();
		for(int i = 0; length > 0; length -= 16,i += 16)
		{

			engine.processBlock(cipher, i, plain, i);
			for (int j = 0; j < 16; j++)
			{
				plain[j+i] = ((byte) (plain[i+j] ^ iv[j]));
			}
			System.arraycopy(cipher, i, iv, 0, 16);
		}
		
		byte[] res = null;
		if(paddingMode == SM4_PKCS8PADDING) {
			res = padding(plain, SM4_DECRYPT);
		}else {
			res = plain;
		}
		return res;
	}
	
	/**
	 * PKCS8PADDING��׼���
	 * @param input ��������
	 * @param mode ����ȥ�����
	 * @return
	 */
	private static byte[] padding(byte[] input, int mode)
	{
		if (input == null)
		{
			return null;
		}

		byte[] ret = (byte[]) null;
		if (mode == SM4_ENCRYPT)
		{
			int p = 16 - input.length % 16;
			ret = new byte[input.length + p];
			System.arraycopy(input, 0, ret, 0, input.length);
			for (int i = 0; i < p; i++)
			{
				ret[input.length + i] = (byte) p;
			}
		}
		else
		{
			int p = input[input.length - 1];
			ret = new byte[input.length - p];
			System.arraycopy(input, 0, ret, 0, input.length - p);
		}
		return ret;
	}
}
